/**
 * Copyright 2017 orivil Authors. All rights reserved.
 * Use of this source code is governed by a MIT-style
 * license that can be found at https://mit-license.org.
 */

package swagger

import (
	"reflect"
	"gitee.com/tugoer/orivil/param"
)

// JSON Schema primitive types
// https://tools.ietf.org/html/draft-zyp-json-schema-04#section-3.5
const (
	SchemaTypeArray   = "array"
	SchemaTypeBoolean = "boolean"
	SchemaTypeInteger = "integer"
	SchemaTypeNumber  = "number"
	SchemaTypeNull    = "null"
	SchemaTypeObject  = "object"
	SchemaTypeString  = "string"
)

// See: http://swagger.io/specification/#schemaObject
type Schema struct {

	// refer to an defined schema
	Ref    string `json:"$ref,omitempty"`

	// The type of the schema.
	Type string `json:"type,omitempty"`

	Format string `json:"format,omitempty"`
	Title  string `json:"title,omitempty"`

	// Required properties
	Required []string `json:"required,omitempty"`

	// GFM syntax can be used for rich text representation
	Description string      `json:"description,omitempty"`
	Default     interface{} `json:"default,omitempty"`

	*Validation

	MaxProperties int `json:"maxProperties,omitempty"`

	MinProperties int `json:"minProperties,omitempty"`

	/**
	The following properties are taken from the JSON Schema definition but their
	definitions were adjusted to the Swagger Specification. Their definition is the
	same as the one from JSON Schema, only where the original definition references
	the JSON Schema(http://swagger.io/specification/#schemaObject) definition, the
	Schema Object definition is used instead.
	*/

	// Required if type is "array". Describes the type of items in the array.
	Items *Items `json:"items,omitempty"`

	// Extend schemas.
	//
	// Example:
	//
	//	var Pet = &Schema{
	//		Type: "object",
	//		Discriminator: "petType",
	//		Properties: Properties{
	//			"name": {Type: "string"},
	//			"petType": {Type: "string"},
	//		},
	//		Required: []string{"name", "petType"},
	//	}
	//
	//	var Cat = &Schema{
	//		Description: "A representation of a cat",
	//		AllOf: []*Schema{
	//			&Schema{
	//				Ref: "#/definitions/Pet",
	//			},
	//			&Schema{
	//				Type: "object",
	//				Properties: Properties{
	//					"huntingSkill": {
	//						Type: "string",
	//						Description: "The measured skill for hunting",
	//						Default: "lazy",
	//						Validation: Validation{
	//							Enum: []interface{}{
	//								"clueless",
	//								"lazy",
	//								"adventurous",
	//								"aggressive",
	//							},
	//						},
	//					},
	//				},
	//				Required: []string{"huntingSkill"},
	//			},
	//		},
	//	}
	AllOf []*Schema `json:"allOf,omitempty"`

	Properties Properties `json:"properties,omitempty"`

	AdditionalProperties Properties `json:"additionalProperties,omitempty"`

	/**
	Other than the JSON Schema subset fields, the following fields may be used for further schema documentation.
	*/

	// Adds support for polymorphism. The discriminator is the schema property name
	// that is used to differentiate between other schema that inherit this schema.
	// The property name used MUST be defined at this schema and it MUST be in the
	// required property list. When used, the value MUST be the name of this schema
	// or any schema that inherits it.
	Discriminator string `json:"discriminator,omitempty"`

	// Relevant only for Schema "properties" definitions. Declares the property as
	// "read only". This means that it MAY be sent as part of a response but MUST NOT
	// be sent as part of the request. Properties marked as readOnly being true SHOULD
	// NOT be in the required list of the defined schema. Default value is false.
	ReadOnly bool `json:"readOnly,omitempty"`

	// This MAY be used only on properties schemas. It has no effect on root schemas.
	// Adds Additional metadata to describe the XML representation format of this property.
	Xml *Xml `json:"xml,omitempty"`

	// Additional external documentation for this schema.
	ExternalDocs *ExternalDoc `json:"externalDocs,omitempty"`

	// A free-form property to include an example of an instance for this schema.
	Example interface{} `json:"example,omitempty"`
}

func (sm *Schema)AppendSchemaProperties(filter *param.FieldNameProvider, schema interface{}) {
	rt := param.ReflectTypeOf(schema, filter, nil)
	rt.Range(func(kind reflect.Kind, field reflect.StructField, pvd param.ConditionProvider) {
		p := &Property{}
		setDataType(kind, p)
		sm.Properties[field.Name] = p
		tag := field.Tag
		format := tag.Get("format")
		if format != "" {
			p.Format = format
		}
		typ := tag.Get("type")
		if typ != "" {
			p.Type = typ
		}
		p.Description += tag.Get("desc")
		if pvd != nil {
			if pvd.Info().Required != nil {
				sm.Required = append(sm.Required, field.Name)
			}
		}
	})
}

func NewJsonTypeSchema(description string, inters... interface{}) *Schema {
	schema := &Schema{Type: SchemaTypeObject, Description: description, Properties: make(Properties, 5)}
	for _, i := range inters {
		schema.AppendSchemaProperties(param.JsonNamePvd, i)
	}
	return schema
}